#include "glWidget.h"
#include <QKeyEvent>
#include <QDebug>
#include <cmath>

GLWidget::GLWidget(QWidget* parent, unsigned int fps)
  : QGLWidget(parent),
    mFramesPainted(0),
    mZoom(-15),
    mTilt(90),
    mSpin(0),
    mTwinkle(false)
{
  connect(&mTimer, SIGNAL(timeout()), this, SLOT(updateGL()));
  mTimer.start(1000/fps);
  mTime.start();
  setFocusPolicy(Qt::StrongFocus);

  qDebug() << "GLWidget started";
}

GLWidget::~GLWidget()
{
  mTimer.stop();
}

void GLWidget::initializeGL()
{
  loadGLTexture("supernova.png", mTextures);
  glEnable(GL_TEXTURE_2D); //enable 2D texture mapping

  glShadeModel(GL_SMOOTH); //smooth shading
  glClearColor(0, 0, 0, 0.5); //black background

  //depth buffer setup
  glClearDepth(1);

  glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); //best perspective correction

  glBlendFunc(GL_SRC_ALPHA, GL_ONE); //blending function based on alpha
  glEnable(GL_BLEND);

  //init stars
  for (int i(0); i < NUM_STARS; ++i)
  {
    mStars[i].mAngle = 0;
    mStars[i].mDistance = i * 5.0 / NUM_STARS;
    mStars[i].mR = rand()%256;
    mStars[i].mG = rand()%256;
    mStars[i].mB = rand()%256;
  }
}

void GLWidget::resizeGL(int w, int h)
{
  glViewport(0, 0, w, h); //reset viewport

  //setup perspective view
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(45, w*1./h, 0.1, 100.);

  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

void GLWidget::paintGL()
{
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //clear screen and depth buffer
  glBindTexture(GL_TEXTURE_2D, mTextures[0]);

  for(int i(0); i < NUM_STARS; ++i)
  {
    glLoadIdentity();
    glTranslatef(0, 0, mZoom); //zoom
    glRotatef(mTilt, 1, 0, 0); //tilt
    glRotatef(mStars[i].mAngle, 0, 1, 0); //rotate star
    glTranslatef(mStars[i].mDistance, 0, 0); //move to star location
    glRotatef(-mStars[i].mAngle, 0, 1, 0); //revert angle
    glRotatef(-mTilt, 1, 0, 0); //revert tiltmStars[i].mR = rand() % 255;

    if (mTwinkle)
    {
      glColor4ub(mStars[NUM_STARS-i-1].mR,
                 mStars[NUM_STARS-i-1].mG,
                 mStars[NUM_STARS-i-1].mB,
                 255);

      //draw twinkling stars
      glBegin(GL_QUADS);
        glTexCoord2f(0, 0); glVertex3f(-1, -1, 0);
        glTexCoord2f(1, 0); glVertex3f(1, -1, 0);
        glTexCoord2f(1, 1); glVertex3f(1, 1, 0);
        glTexCoord2f(0, 1); glVertex3f(-1, 1, 0);
      glEnd();
    }

    //draw normal stars
    glRotatef(mSpin, 0, 0, 1);
    glColor4ub(mStars[i].mR, mStars[i].mG, mStars[i].mB, 255);
    glBegin(GL_QUADS);
      glTexCoord2f(0, 0); glVertex3f(-1, -1, 0);
      glTexCoord2f(1, 0); glVertex3f(1, -1, 0);
      glTexCoord2f(1, 1); glVertex3f(1, 1, 0);
      glTexCoord2f(0, 1); glVertex3f(-1, 1, 0);
    glEnd();

    //move stars
    mSpin += 0.01;
    mStars[i].mAngle += i * 1.0 / NUM_STARS;
    mStars[i].mDistance -= 0.01;
    if (mStars[i].mDistance < 0)
    {
      mStars[i].mDistance += 5;
      mStars[i].mR = rand() % 255;
      mStars[i].mG = rand() % 255;
      mStars[i].mB = rand() % 255;
    }
  }

  //calculate fps
  ++mFramesPainted;
  if (mTime.elapsed() > 1000)
  {
    emit fps(mFramesPainted * 1000 / mTime.restart());
    mFramesPainted = 0;
  }
}

void GLWidget::keyPressEvent(QKeyEvent *e)
{
  switch (e->key())
  {
  case Qt::Key_PageUp:
    mZoom -= 0.02;
    break;
  case Qt::Key_PageDown:
    mZoom += 0.02;
    break;
  case Qt::Key_Up:
    mTilt -= 0.1;
    break;
  case Qt::Key_Down:
    mTilt += 0.1;
    break;
  case Qt::Key_T:
    mTwinkle = !mTwinkle;
    break;
  default:
    QGLWidget::keyPressEvent(e);
  }
}

void GLWidget::loadGLTexture(const QString &filename, GLuint textures[3])
{
  //load image
  QImage temp(filename);
  if (temp.isNull())
  {
    temp = QImage(16, 16, QImage::Format_RGB32);
    temp.fill(Qt::green);
  }
  QImage glformatted(QGLWidget::convertToGLFormat(temp)); //convert to gl format

  glGenTextures(1, &textures[0]); //create 1 texture

  glBindTexture(GL_TEXTURE_2D, textures[0]); //create 2d texture at ret
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); //filter when image is smaller than texture: good filter
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); //filter when image is larger than texture: good filter
  glTexImage2D(GL_TEXTURE_2D, //create 2d texture
               0, //level of detail
               3, //number of data components (rgb = 3)
               glformatted.width(), glformatted.height(), //size of texture
               0, //border
               GL_RGBA, //use rgb in this order
               GL_UNSIGNED_BYTE, //image data is unsigned bytes
               glformatted.bits()); //pointer to image data
}
